#include <fstream>
#include <iostream>
#include <cstdlib>
#include <cmath>

using namespace std;

Scheduler::Scheduler(string filename)
{	//filename is guaranteed to be a valid file, by our main. Not necessarily the file we need though.
	ifstream ifs(filename.c_str());
	string buffer;

	getline(ifs, buffer);
	int numCPUs = atoi(buffer.c_str());

	getline(ifs, buffer);
	numCores = numCPUs * atoi(buffer.c_str());
	Cores = new Task*[numCores];
	for(int i = 0; i < numCores; ++i)
	{
		Cores[i] = 0;
	}

	getline(ifs, buffer);
	contextSwitchTime = atoi(buffer.c_str());	

	getline(ifs, buffer);
	RRQuantum = atoi(buffer.c_str());

	getline(ifs, buffer);
	numProcesses = atoi(buffer.c_str());
	SJFTasks = new Task[numProcesses];
	RRTasks = new Task[numProcesses];
	FCFSTasks = new Task[numProcesses];

	getline(ifs, buffer);
	if(buffer.substr(0, 6).compare("LAMBDA") == 0)
	{
		double lambda = atof(buffer.substr(7).c_str());
		generateArrays(lambda);
	}
	else
	{		
		FCFSTasks = new Task[numProcesses];
		RRTasks = new Task[numProcesses];
		SJFTasks = new Task[numProcesses];
		int processTime = atoi(buffer.c_str());
		FCFSTasks[0] = Task(processTime);
		RRTasks[0] = Task(processTime);
		SJFTasks[0] = Task(processTime);
		for(int i = 1; i < numProcesses; ++i)
		{
			getline(ifs, buffer);
			processTime = atoi(buffer.c_str());	
			FCFSTasks[i] = Task(processTime);
			RRTasks[i] = Task(processTime);
			SJFTasks[i] = Task(processTime);
		}
		sortSJF();
	}	
}

void Scheduler::generateArrays(double lambda)
{
	FCFSTasks = new Task[numProcesses];
	RRTasks = new Task[numProcesses];
	SJFTasks = new Task[numProcesses];
	srand(time(NULL));
	for(int i = 0; i < numProcesses; ++i)
	{
		double randNum = log(1.0 - ((double)rand()/RAND_MAX));
		randNum /= lambda * -1;
		int processTime = (int) randNum;
		FCFSTasks[i] = Task(processTime);
		RRTasks[i] = Task(processTime);
		SJFTasks[i] = Task(processTime);
	}
	sortSJF();
}

void Scheduler::sortSJF()
{
	for(int j = 0; j < numProcesses - 1; ++j)
	{//Don't feel like writing quicksort here. Insertion it is.
		int lowest = SJFTasks[j].cyclesNeeded,
			lowestPos = j;	
		for(int k = j + 1; k < numProcesses; ++k)
		{
			if(SJFTasks[k].cyclesNeeded < lowest)
			{
				lowest = SJFTasks[k].cyclesNeeded;
				lowestPos = k;
			}
		}
		if(lowestPos != j)
		{
			SJFTasks[lowestPos].cyclesNeeded = SJFTasks[j].cyclesNeeded;
			SJFTasks[j] = lowest;
		}
	}
}

void Scheduler::runSJF(){
	compCheck=0;
	nextProcessIndex=0;
	
	for(int i=0;i<numCores;++i){//obliterating memory block so no ghost data
		Cores[i]=0;
	}
	
	while(true){
		//for every task in the array, run a cycle
		for(int i=0;i<numCores;++i){
			if(Cores[i]==0 || Cores[i]->taskState == Task::complete){//If the core is free for another task, put another task on it
				Cores[i]=&SJFTasks[nextProcessIndex];
				SJFTasks[nextProcessIndex].taskState=Task::swapping;
				SJFTasks[nextProcessIndex].swapCounter = contextSwitchTime;
				nextProcessIndex++;
			}
		}
		for(int i=0;i<numProcesses;++i){//iterate through the processes and run a cycle on each task
			SJFTasks[i].runCycle();
			if (SJFTasks[i].taskState == Task::complete) compCheck++;
		}
		if (compCheck<numProcesses) compCheck=0;
		else return;
	}
}

void Scheduler::runRR(){
	compCheck=0;
	nextProcessIndex=0;
	int coreIndices[numCores];
	for(int i=0;i<numCores;++i){
		Cores[i]=0;
	}

	bool bKeepSwapping = numProcesses > numCores;
		
	while(true){
		for(int i=0;i<numCores;++i){
			if(Cores[i]==0 || (Cores[i]->taskState == Task::complete && bKeepSwapping)){//if the core is free for another task, and another task is available, put that task on it
				Cores[i]=&RRTasks[nextProcessIndex];
				coreIndices[i] = nextProcessIndex;
				RRTasks[nextProcessIndex].taskState=Task::swapping;
				RRTasks[nextProcessIndex].swapCounter=contextSwitchTime;
				RRTasks[nextProcessIndex].cyclesInCore=0;
				int startingPoint = nextProcessIndex;
				do 
				{			
					++nextProcessIndex;
					if(nextProcessIndex >= numProcesses) nextProcessIndex= 0;
					if(nextProcessIndex == startingPoint){ bKeepSwapping = false; break;}
				}while(RRTasks[nextProcessIndex].taskState != Task::waiting);
			} else{//else, the core is occupied by previous task
				//check how long the task has been on there
				//if the process.cyclesInCore is greater than the round robin quantum then kick it off
				if(Cores[i]->cyclesInCore >= RRQuantum && bKeepSwapping){
					Cores[i]->taskState= Cores[i]->taskState == Task::complete ? Task::complete : Task::waiting;
					Cores[i]=&RRTasks[nextProcessIndex];
					coreIndices[i] = nextProcessIndex;
					RRTasks[nextProcessIndex].taskState=Task::swapping;
					RRTasks[nextProcessIndex].swapCounter=contextSwitchTime;
					RRTasks[nextProcessIndex].cyclesInCore=0;
					int startingPoint = nextProcessIndex;
					do 
					{
						++nextProcessIndex;
						if(nextProcessIndex >= numProcesses) nextProcessIndex= 0;
						if(nextProcessIndex == startingPoint) { bKeepSwapping = false; break;}
					}while(RRTasks[nextProcessIndex].taskState != Task::waiting);
				}
			}
		}
		for(int i=0;i<numProcesses;++i){
			if (RRTasks[i].taskState == Task::active){
				RRTasks[i].cyclesInCore++;
			}
			RRTasks[i].runCycle();
			if (RRTasks[i].taskState == Task::complete) compCheck++;
		}
		if (compCheck<numProcesses)
		{
			compCheck=0;
		}
		else return;
	}
}

void Scheduler::runFCFS(){
	compCheck=0;
	nextProcessIndex=0;
	
	for(int i=0;i<numCores;++i){
		Cores[i]=0;
	}
	
	while(true){
		//for every task in the array, run a cycle
		for(int i=0;i<numCores;++i){
			if(Cores[i]==0 || Cores[i]->taskState == Task::complete){
				Cores[i]=&FCFSTasks[nextProcessIndex];
				FCFSTasks[nextProcessIndex].taskState = Task::swapping;
				FCFSTasks[nextProcessIndex].swapCounter = contextSwitchTime;
				nextProcessIndex++;
			}
		}
		for(int i=0;i<numProcesses;++i){
			FCFSTasks[i].runCycle();
			if (FCFSTasks[i].taskState == Task::complete) compCheck++;
		}
		if (compCheck<numProcesses) compCheck=0;
		else return;
	}
}
	
void Scheduler::runSimulation(){
	runSJF();	
	cout << "Shortest Job First completed. Statistics follow." << endl;
	processData(SJFTasks);	
	cout << endl;
	runRR();
	cout << "Round Robin completed. Statistics follow." << endl;
	processData(RRTasks);
	cout << endl;
	runFCFS();
	cout << "First Come First Serve completed. Statistics follow." << endl;
	processData(FCFSTasks);
}

void Scheduler::processData(Task* taskList)
{
	int totalWait = 0, maxWait = 0;
	float mean, median, variance, stddev;
	for(int i = 0; i < numProcesses; ++i)
	{
		totalWait += taskList[i].cyclesWaited;
		maxWait = max(maxWait, taskList[i].cyclesWaited);
	}
	mean = (float)totalWait / (float)numProcesses;
	cout << "Maximum Wait Time: " << maxWait << endl;
	cout << "Mean Wait Time: " << mean << endl;
	for(int i = 0; i < numProcesses; ++i)
	{
		variance += pow((float)taskList[i].cyclesWaited - mean, 2.0f);
	}
	variance /= numProcesses;
	stddev = sqrt(variance);
	cout << "Standard Deviation: " << stddev << endl;
}
